namespace WP7Contrib.Caching
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using Logging;
    using Serialization;

    internal sealed class SilverlightSerializerValidator
    {
        private readonly ILog log;

        private static readonly List<Type> IgnoreList;

        static SilverlightSerializerValidator()
        {
            IgnoreList = new List<Type>
                             {
                                 typeof (string),
                                 typeof (List<string>),
                                 typeof (string[]),
                                 typeof (char),
                                 typeof (char?),
                                 typeof (List<char>),
                                 typeof (char[]),
                                 typeof (bool),
                                 typeof (bool?),
                                 typeof (List<bool>),
                                 typeof (bool[]),
                                 typeof (byte),
                                 typeof (byte?),
                                 typeof (List<byte>),
                                 typeof (byte[]),
                                 typeof (ushort),
                                 typeof (ushort?),
                                 typeof (List<ushort>),
                                 typeof (ushort[]),
                                 typeof (short),
                                 typeof (short?),
                                 typeof (List<short>),
                                 typeof (short[]),
                                 typeof (uint),
                                 typeof (uint?),
                                 typeof (List<uint>),
                                 typeof (uint[]),
                                 typeof (int),
                                 typeof (int?),
                                 typeof (List<int>),
                                 typeof (int[]),
                                 typeof (ulong),
                                 typeof (List<ulong>),
                                 typeof (ulong[]),
                                 typeof (long),
                                 typeof (long?),
                                 typeof (List<long>),
                                 typeof (long[]),
                                 typeof (float),
                                 typeof (float?),
                                 typeof (List<float>),
                                 typeof (float[]),
                                 typeof (double),
                                 typeof (double?),
                                 typeof (List<double>),
                                 typeof (double[]),
                                 typeof (decimal),
                                 typeof (decimal?),
                                 typeof (List<decimal>),
                                 typeof (decimal[]),
                                 typeof (DateTime),
                                 typeof (DateTime?),
                                 typeof (List<DateTime>),
                                 typeof (DateTime[]),
                                 typeof (Dictionary<string, string>),
                                 typeof (Dictionary<string, double>)
                             };
        }

        public SilverlightSerializerValidator(ILog log)
        {
            this.log = log;
        }

        public bool CanBeIgnored(Type type)
        {
            return IgnoreList.Contains(type);
        }

        public bool CanBeSerialized(object instance, ref IEnumerable<Type> failingTypes)
        {
            var graph = this.ExtractGraph(instance);
            failingTypes = this.CheckNodesCanBeSerialized(graph);

            this.LogGraph(graph);
            return graph.State == SerializationState.Succeeded;
        }

        private SerializationNode ExtractGraph(object root)
        {
            return this.ExtractGraph(new SerializationNode(root));
        }

        private SerializationNode ExtractGraph(SerializationNode node)
        {
            if (!node.HasValue)
            {
                return node;
            }

            var propertyInfos = node.Value.GetType().GetProperties();
            foreach (var propertyInfo in propertyInfos)
            {
                object value;
                try
                {
                    value = propertyInfo.GetValue(node.Value, null);
                }
                catch (Exception)
                {
                    continue;
                }

                if ( value == null)
                {
                    continue;
                }

                if (node.Root.ContainsType(propertyInfo.PropertyType))
                {
                    continue;
                }

                this.log.Write("Property Name - '{0}', value - '{1}'", propertyInfo.Name, value);

                if (!this.CanBeIgnored(propertyInfo.PropertyType))
                {
                    var childNode = node.AddChild(propertyInfo.Name, propertyInfo.GetValue(node.Value, null));

                    if (value is IEnumerable == false)
                    {
                        this.ExtractGraph(childNode);    
                    }
                }
            }

            return node;
        }

        private  List<Type> CheckNodesCanBeSerialized(SerializationNode node)
        {
            var failingTypes = new List<Type>();

            try
            {
                if (node.HasValue)
                {
                    if (node.HasParent)
                    {
                        var propertyInfo = node.Parent.GetType().GetProperty(node.Name);
                        if (propertyInfo != null)
                        {
                            var attrbiutes = propertyInfo.GetCustomAttributes(typeof(DoNotSerialize), false);
                            if (attrbiutes.Length == 1)
                            {
                                node.State = SerializationState.Skipped;
                                return failingTypes;
                            }
                        }
                    }
                    
                    var result = SilverlightSerializer.Serialize(node.Value);
                    SilverlightSerializer.Deserialize(result);

                    node.State = SerializationState.Succeeded;
                    this.SetDescendantsStates(node, SerializationState.Skipped);
                }
                else
                {
                    node.State = SerializationState.Skipped;
                }
            }
            catch (Exception)
            {
                failingTypes.Add(node.Value.GetType());

                node.State = SerializationState.Failed;
                foreach (var childNode in node.Children)
                {
                    failingTypes.AddRange(this.CheckNodesCanBeSerialized(childNode));
                }
            }

            return failingTypes;
        }

        private void SetDescendantsStates(SerializationNode node, SerializationState state)
        {
            foreach (var childNode in node.Children)
            {
                childNode.State = state;
                this.SetDescendantsStates(childNode, state);
            }
        }

        private void LogGraph(SerializationNode node)
        {
            var output = new StringBuilder();
            
            this.LogGraph(node, output);
            this.log.Write(output.ToString());
        }
        
        private StringBuilder LogGraph(SerializationNode node, StringBuilder sb)
        {
            if (!node.HasParent)
            {
                sb.AppendLine(string.Format("{0} ({1})", node.Name, node.State));
            }

            if (node.HasChildren)
            {
                foreach (var child in node.Children)
                {
                    var childSb = new StringBuilder();
                    var current = child;
                    while (current.HasParent)
                    {
                        childSb.Insert(0, string.Format(" -> '{0}' ({1})", current.Name, current.State));
                        current = current.Parent;
                    }

                    childSb.Insert(0, string.Format("{0} ({1})", current.Name, current.State));
                    sb.AppendLine(childSb.ToString());

                    this.LogGraph(child, sb);
                }
            }

            return sb;
        }
    }
}